package com.dycong.common.IO.nio;

import lombok.extern.log4j.Log4j2;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;

import static java.nio.channels.SelectionKey.OP_READ;

/**
 * Created by dycong on 2017/3/9.
 */

@Log4j2
public class TestIntBuffer {
//    private static final Logger log = LoggerFactory.getLogger(TestIntBuffer.class);


    public static void main(String[] args) throws Exception {
//        testChanel();
//        testIntBuffer();
//        testTrabs();
//        testSelect();
//        testDatagramChannelSend();
//        testReadAndWrite();
//        testThreadFileChannelMap();
    testFlip();
    }

    /**
     *todo 关于flip(),每次flip()，操作：limit ->position ;position ->0; 所以，如果当前position 位置为0的话，flip()之后，limit即变为0，
     *todo 不能再put()进数据了，慎用flip() ，以防数据丢失
     * Method threw 'java.lang.IndexOutOfBoundsException' exception.
     * Method threw 'java.nio.BufferOverflowException' exception.
     */
    public static void testFlip(){
        ByteBuffer buffer=ByteBuffer.allocate(100);
        System.out.println("capacity: "+buffer.capacity()+"-limit: "+buffer.limit()+"-position: "+buffer.position());
        buffer.put("1234567891011121314151617181920".getBytes());
        System.out.println("capacity: "+buffer.capacity()+"-limit: "+buffer.limit()+"-position: "+buffer.position());
        buffer.flip();
        System.out.println("capacity: "+buffer.capacity()+"-limit: "+buffer.limit()+"-position: "+buffer.position());
        buffer.put("123456789".getBytes());
        System.out.println("capacity: "+buffer.capacity()+"-limit: "+buffer.limit()+"-position: "+buffer.position());
        buffer.flip();
        System.out.println("capacity: "+buffer.capacity()+"-limit: "+buffer.limit()+"-position: "+buffer.position());
    }

    public static void testThreadFileChannelMap() throws Exception {
        Path copy_from = Paths.get("D:/电影珍藏/[电影天堂www.dygod.cn]初恋50次BD中英双字.rmvb");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                FileChannel fileChannel=null;
                try {
                    fileChannel = new RandomAccessFile(copy_from.toFile(), "rw").getChannel();
                    //TODO map()返回的MappedByteBuffer capacity为返回时的固定值，limit=capacity,position为0
                    MappedByteBuffer byteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size());
                    byteBuffer.put("asdfghjkl".getBytes());
                    byteBuffer.force();
                    System.out.println(fileChannel.size());
                } catch (Exception e) {

                }finally {
                    try {
                        if (fileChannel!=null)
                        fileChannel.close();
                    }catch (Exception e){

                    }
                }
            }
        };
        new Thread(runnable).start();
        Thread.sleep(200);
        new Thread(runnable).start();
        Thread.sleep(200);
        new Thread(runnable).start();
    }

    /*Java com.dycong.common.IO.nio 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道，从source通道读取。*/
    public static void testPipe() {
        Pipe pipe = null;
        Pipe.SinkChannel sinkChannel = null;
        Pipe.SourceChannel sourceChannel = null;
        try {
            pipe = Pipe.open();
            sinkChannel = pipe.sink();

            String newData = "New String to write to file..." + System.currentTimeMillis();
            ByteBuffer buf = ByteBuffer.allocate(48);
            buf.clear();
            buf.put(newData.getBytes());
            buf.flip();

            while (buf.hasRemaining()) {
                sinkChannel.write(buf);
            }


            sourceChannel = pipe.source();
            ByteBuffer buf1 = ByteBuffer.allocate(1024);
            int bytesRead = sourceChannel.read(buf1);
            while (bytesRead != -1) {
                buf1.flip();
                //do Something with buf1
                buf1.clear();
                bytesRead = sourceChannel.read(buf1);
            }
        } catch (IOException e) {
            log.error("", e);
        } finally {
            try {
                sinkChannel.close();
                ;
                sourceChannel.close();
            } catch (IOException e) {
                log.error("", e);
            }
        }
    }

    /**
     * TCP SocketChannel的read()方法可以通过read()方法返回值判断读了多少字节，或则是否读完
     * UDP DatagramChannel的read()方法，由于是无连接的网络通道，所以不能像其他通道那样读取和写入，如果指定的Buffer容不下收到的数据，多出的数据将会被丢弃。
     */
    //TODO java NIO中的DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议，所以不能像其它通道那样读取和写入。它发送和接收的是数据包。
    public static void testDatagramChannelReceive() {
        DatagramChannel datagramChannel = null;
        try {
            datagramChannel = DatagramChannel.open();
            //TODO 这个例子打开的 DatagramChannel可以在UDP端口9999上接收数据包
            datagramChannel.socket().bind(new InetSocketAddress(10000));
            //接收数据
            ByteBuffer bufferReceive = ByteBuffer.allocate(1024);
            bufferReceive.clear();

            //TODO receive()方法会将接收到的数据包内容复制到指定的Buffer，如果该Buffer容不下收到的数据，多出的数据将会被丢弃。
            //TODO receive()方法会阻塞等到读取到内容并返回SocketAddress；
            SocketAddress socketAddress = datagramChannel.receive(bufferReceive);

            //TODO 非阻塞模式下receive()方法不会阻塞等到SocketAddress，receive()方法会在调用后立即返回，
            //TODO 如果还没有新进来的连接，返回的将是Null。因此，需要检查返回的SocketAddress是否为null。
            datagramChannel.configureBlocking(false);
            while (true) {
                SocketAddress socketAddress1 = datagramChannel.receive(bufferReceive);
                if (null != socketAddress1) {
                    //do Something whit bufferReceive
                }
            }
        } catch (IOException e) {
            log.error("", e);
        } finally {
            try {
                datagramChannel.close();
            } catch (IOException e) {
                log.error("", e);
            }
        }
    }

    public static void testDatagramChannelSend() {
        DatagramChannel datagramChannel = null;
        try {
            datagramChannel = DatagramChannel.open();
            //发送数据
            String newData = "New String to write to file..." + System.currentTimeMillis();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.clear();
            buffer.put(newData.getBytes());
            buffer.flip();
            int byteSend = datagramChannel.send(buffer, new InetSocketAddress("", 10000));
            //非阻塞模式
            datagramChannel.configureBlocking(false);
            buffer.flip();
            while (buffer.hasRemaining()) {
                int bytes = datagramChannel.send(buffer, new InetSocketAddress("", 10000));
            }
//可以将DatagramChannel“连接”到网络中的特定地址的。由于UDP是无连接的，连接到特定地址并不会像TCP通道那样创建一
// 个真正的连接。而是锁住DatagramChannel ，让其只能从特定地址收发数据。当连接后，也可以使用read()和write()方法，
// 就像在用传统的通道一样。只是在数据传送方面没有任何保证。
            DatagramChannel datagramChannel1 = DatagramChannel.open();
            datagramChannel1.connect(new InetSocketAddress("", 10000));
            //todo read()方法会将接收到的数据包内容复制到指定的Buffer，如果该Buffer容不下收到的数据，多出的数据将会被丢弃。
            int bytesRead = datagramChannel1.read(buffer);
            int bytesWrite = datagramChannel1.write(buffer);

        } catch (IOException e) {
            log.error("", e);
        } finally {
            try {
                datagramChannel.close();
            } catch (IOException e) {
                log.error("", e);
            }
        }
    }


    //TODO Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。
    public static void testServerSocketChannel() {
        ServerSocketChannel serverSocketChannel = null;
        try {
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            while (true) {
                //TODO 阻塞模式下accept()方法会阻塞等到SocketChanel；
                SocketChannel socketChannel = serverSocketChannel.accept();

                //TODO 非阻塞模式下accept()方法不会阻塞等到SocketChanel，accept()方法会在调用后立即返回，
                //TODO 如果还没有新进来的连接，返回的将是Null。因此，需要检查返回的SocketChanel是否为null。
                while (true) {
                    SocketChannel socketChannel1 = serverSocketChannel.accept();

                    if (null != socketChannel) {
                        //do Something...
                    }
                }
                //do Something......
            }

        } catch (IOException e) {
            log.error("", e);
        } finally {
            try {
                serverSocketChannel.close();
            } catch (IOException e) {
                log.error("", e);
            }
        }

    }


    //打开一个SocketChannel并连接到互联网上的某台服务器。一个新连接到达ServerSocketChannel时，会创建一个SocketChannel。
    public static void testSocketChannel() {
        SocketChannel socketChannel = null;
        try {
            socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("www.baidu.com", 80));

            //1.Read......
            ByteBuffer bufferRead = ByteBuffer.allocate(1024);
            //TODO 从Socket读取数据，read()返回的int值表示读了多少字节进Buffer里。如果返回的是-1，表示已经读到流的末尾
            //TODO 非阻塞模式下read()方法在尚未读取到任何数据时可能就返回了。
            long count = socketChannel.read(bufferRead);
            while (count != -1) {
                bufferRead.flip();
                //do Something with buffer
                count = socketChannel.read(bufferRead);
                bufferRead.clear();
            }
            ;
            //2. Write...
            String data = "new String to write to socket...";

            ByteBuffer bufferWrite = ByteBuffer.allocate(1024);
            bufferWrite.clear();
            bufferWrite.put(data.getBytes());
            bufferWrite.flip();
            while (bufferWrite.hasRemaining()) {
                //TODO write()方法放在while循环中，write()方法无法保障能写多少字节。所以，我们重复调用write()直到Buffer没有要写的为止
                //TODO 非阻塞模式下，write()方法在尚未写出任何内容时可能就返回了
                socketChannel.write(bufferWrite);
            }

        } catch (IOException e) {
            log.error("", e);
        } finally {
            try {
                socketChannel.close();
            } catch (IOException e) {
                log.error("", e);
            }
        }
    }

    /*Selector（选择器）是Java NIO中能够检测一到多个NIO通道，并能够知晓通道是否为诸如读写事件做好准备的组件。这样，一个单独的线程可以管理多个channel，从而管理多个网络连接。
    * 与Selector一起使用时，Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用，因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。*/
    private static final int BUF_SIZE = 256;
    private static final int TIMEOUT = 3000;

    public static void testSelect() {
        try {
            // 打开服务端 Socket
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

            // 打开 Selector
            Selector selector = Selector.open();

            // 服务端 Socket 监听8080端口, 并配置为非阻塞模式
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            serverSocketChannel.configureBlocking(false);

            // 将 channel 注册到 selector 中.
            // 通常我们都是先注册一个 OP_ACCEPT 事件, 然后在 OP_ACCEPT 到来时, 再将这个 Channel 的 OP_READ
            // 注册到 Selector 中.
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                // 通过调用 select 方法, 阻塞地等待 channel I/O 可操作
                if (selector.select(TIMEOUT) == 0) {
                    System.out.print(".");
                    continue;
                }

                // 获取 I/O 操作就绪的 SelectionKey, 通过 SelectionKey 可以知道哪些 Channel 的哪类 I/O 操作已经就绪.
                Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();

                while (keyIterator.hasNext()) {

                    SelectionKey key = keyIterator.next();

                    // 当获取一个 SelectionKey 后, 就要将它删除, 表示我们已经对这个 com.dycong.common.IO 事件进行了处理.
                    keyIterator.remove();

                    if (key.isAcceptable()) {
                        // 当 OP_ACCEPT 事件到来时, 我们就有从 ServerSocketChannel 中获取一个 SocketChannel,
                        // 代表客户端的连接
                        // 注意, 在 OP_ACCEPT 事件中, 从 key.channel() 返回的 Channel 是 ServerSocketChannel.
                        // 而在 OP_WRITE 和 OP_READ 中, 从 key.channel() 返回的是 SocketChannel.
                        SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
                        clientChannel.configureBlocking(false);
                        //在 OP_ACCEPT 到来时, 再将这个 Channel 的 OP_READ 注册到 Selector 中.
                        // 注意, 这里我们如果没有设置 OP_READ 的话, 即 interest set 仍然是 OP_CONNECT 的话, 那么 select 方法会一直直接返回.
                        clientChannel.register(key.selector(), OP_READ, ByteBuffer.allocate(BUF_SIZE));
                    }

                    if (key.isReadable()) {
                        SocketChannel clientChannel = (SocketChannel) key.channel();
                        ByteBuffer buf = (ByteBuffer) key.attachment();
                        long bytesRead = clientChannel.read(buf);
                        if (bytesRead == -1) {
                            clientChannel.close();
                        } else if (bytesRead > 0) {
                            key.interestOps(OP_READ | SelectionKey.OP_WRITE);
                            System.out.println("Get data length: " + bytesRead);
                        }
                    }

                    if (key.isValid() && key.isWritable()) {
                        ByteBuffer buf = (ByteBuffer) key.attachment();
                        buf.flip();
                        SocketChannel clientChannel = (SocketChannel) key.channel();

                        clientChannel.write(buf);

                        if (!buf.hasRemaining()) {
                            key.interestOps(OP_READ);
                        }
                        buf.compact();
                    }
                }
            }
        } catch (Exception e) {

        }
            /*
            boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
            boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
            boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
            boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;

            selectionKey.isAcceptable();
            selectionKey.isConnectable();
            selectionKey.isReadable();
            selectionKey.isWritable();
             */
    }

    private static ByteBuffer buffer = ByteBuffer.allocate(2024);

    protected void readDataFromSocket(SelectionKey key) throws Exception {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        int count;
        buffer.clear(); // 清空Buffer
        // Loop while data is available; channel is nonblocking
        //TODO 当可以读到数据时一直循环，通道为非阻塞,read()方法返回的int值表示了有多少字节被读到了Buffer中。如果返回-1，表示到了文件末尾。
        while ((count = socketChannel.read(buffer)) > 0) {
            buffer.flip(); // 将缓冲区置为可读
            // Send the data; don't assume it goes all at once
            //TODO 发送数据，不要期望能一次将数据发送完,因为无法保证write()方法一次能向FileChannel写入多少字节，因此需要重复调用write()方法，直到Buffer中已经没有尚未写入通道的字节。
            while (buffer.hasRemaining()) {
                socketChannel.write(buffer);
            }
            // WARNING: the above loop is evil. Because
            // it's writing back to the same nonblocking
            // channel it read the data from, this code can
            // potentially spin in a busy loop. In real life
            // you'd do something more useful than this.
            //这里的循环是无意义的，具体按实际情况而定
            buffer.clear(); // Empty buffer
        }
        if (count < 0) {
            // Close channel on EOF, invalidates the key
            //读取结束后关闭通道，使key失效,
            socketChannel.close();
        }
    }

    //TODO 可以通过任何FileInputStream、FileOutputStream,RandomAccessFile得到Channel
    public static void testChanel() {

        try (RandomAccessFile file = new RandomAccessFile("F:/资料/oms.20160818.sql", "rw")) {
            FileChannel channel = file.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(16);
            int count = channel.read(buffer);
            while (count != -1) {
                if (count != -1) {
                    buffer.flip();
                    while (buffer.hasRemaining())
                        System.out.println(buffer.get());
                }
                System.out.println("--------------");
                buffer.clear();
                count = channel.read(buffer);
            }
        } catch (FileNotFoundException ex) {
            throw new RuntimeException(ex);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }


    public static void testIntBuffer() {
        IntBuffer buf = IntBuffer.allocate(20);//设置缓冲区容量为20个大小
        System.out.println("缓冲区写入数据之前：");
        System.out.println(buf.capacity());//调用capacity方法返回buf容量
        System.out.println(buf.position());//调用position方法返回buf的位置
        System.out.println(buf.limit());//调用limit方法返回buf的限制
        int temp[] = {1, 2, 3, 4};
        int i = 0;
        buf.put(i);//写入i
        buf.put(temp);//写入数组temp
        System.out.println("缓冲区写入数据之后：");
        System.out.println(buf.capacity());//调用capacity方法返回buf容量
        System.out.println(buf.position());//调用position方法返回buf的位置
        System.out.println(buf.limit());//调用limit方法返回buf的限制
        System.out.println("缓冲区内容：");
        buf.flip();//反转此缓冲区:The limit is set to the current position and then the position is set to zero

        int[] dst = new int[buf.limit()];
//        System.out.println(buf.get(dst));
        while (buf.hasRemaining()) {
            int x = buf.get();//相对 get 方法。读取此缓冲区当前位置的 int，然后该位置递增。
            System.out.println(x);
        }
    }

    public static void testReadAndWrite() {
        try (RandomAccessFile fileFrom = new RandomAccessFile("D:/log/portal/03101.log", "rw")) {
            FileChannel fileChannelFrom = fileFrom.getChannel();
            long amount = fileChannelFrom.size();
            //TODO transferFrom(ReadableByteChannel src,long position, long count)从位置position（file的position）开始写入，写入count，没有被覆盖的部分不会被删除。
            //TODO FileChannel也是可以边读边写的双向通道
            fileChannelFrom.transferFrom(fileChannelFrom, 0L, amount);
        } catch (FileNotFoundException e) {
            log.info("", e);
        } catch (IOException e) {
            log.info("", e);
        }
    }

    public static void testTransferFrom() {

        try (RandomAccessFile fileFrom = new RandomAccessFile("D:/log/portal/portal2.log", "rw");
             RandomAccessFile fileTo = new RandomAccessFile("D:/log/portal/test.log", "rw")) {
            FileChannel fileChannelFrom = fileFrom.getChannel();
            FileChannel fileChannelTo = fileTo.getChannel();
            long amount = fileChannelFrom.size();
            fileChannelTo.transferFrom(fileChannelFrom, 0L, amount);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void scatterGather() {

        try (RandomAccessFile accessFile = new RandomAccessFile("", "r")) {
            FileChannel fileChannel = accessFile.getChannel();
            ByteBuffer header = ByteBuffer.allocate(128);
            ByteBuffer body = ByteBuffer.allocate(1024);
            ByteBuffer[] param = {header, body};
            fileChannel.read(param);
            fileChannel.write(param);
        } catch (Exception e) {
        }


    }
}
